package pl.edu.agh.jemo.evolution.operator.mutation.impl;

import javax.swing.JPanel;

import pl.edu.agh.jemo.evolution.common.JemoRandom;
import pl.edu.agh.jemo.evolution.genotype.Genotype;
import pl.edu.agh.jemo.evolution.operator.mutation.Mutation;
import pl.edu.agh.jemo.evolution.specimen.Domination;
import pl.edu.agh.jemo.evolution.specimen.Specimen;
import pl.edu.agh.jemo.gui.panels.mutation.HillClimbingConfigPanel;

/**
 * Hillclimbing moves given specimen, specified distance in random direction.
 * This mutation is used to improve specimen, so if mutated specimen is weaker, then its parent, specimen is not changed.
 * Specimen is moved up to value given by multiplier, which should be set by user depending on objective function domain size.
 * 
 * Note that this mutation has no built-in domain control. It should either be performed outside mutation, or objective function should be constructed in a way, that specimen out of domain will always be weaker, then specimen in domain.
 * Note also that this mutation works only on 2 dimensional problems.
 * 
 * @author Marcin Tkacz
 *
 */
public class Hillclimbing implements Mutation {

	/**
	 * Multiplier parameter is used to modify maximal specimen change.
	 */
	protected double multiplier = 1.;
	
	/**
	 * Slightly modifies specimen genotype, trying to move it closer to extremum.
	 * Modifies both dimensions of specimen genotype. If it has more than two dimensions, the rest of them is skipped.
	 * New specimen is moved by (-multiplier ; multiplier) range. Bigger step on one dimension results with smaller step on another.
	 * if moved specimen is stronger than its parent, given specimen's genotype is modified.
	 * 
	 * @param specimen Reference to specimen for which mutation is called.
	 */
	@Override
	public void performMutation(Specimen specimen) {
		double crossPoint = JemoRandom.getRandom().nextDouble();
		crossPoint = crossPoint * multiplier;
		final Specimen newSpecimen = specimen.clone();
		final Genotype[] genotypeRef = newSpecimen.getGenotype();
		genotypeRef[0].fromDouble(genotypeRef[0].asDouble() + (JemoRandom.getRandom().nextBoolean() ? 1. : -1.)	* crossPoint);
		genotypeRef[1].fromDouble(genotypeRef[1].asDouble() + (JemoRandom.getRandom().nextBoolean() ? 1. : -1.)	* (1 * multiplier - crossPoint));

		if (!newSpecimen.isInDomain()) {
			newSpecimen.moveToDomain();
		}
		
		newSpecimen.calculatePhenotype();
		specimen.calculatePhenotype();
		
		if (Domination.DOMINATES == newSpecimen.dominationRelationWith(specimen)) {
			specimen.setGenotype(genotypeRef);
		}
	}

	/**
	 * Returns currently set multiplier vale.
	 * Multiplier is a parameter allowing to modify mutation step.
	 * 
	 * @return Value of multiplier parameter.
	 */
	public double getMultiplier() {
		return multiplier;
	}

	/**
	 * Stores new multiplier value and uses it for step range modification.
	 * 
	 * @param multiplier Value of currently set multiplier parameter.
	 */
	public void setMultiplier(double multiplier) {
		this.multiplier = multiplier;
	}

	/**
	 * Returns configuration panel being extension of JPanel instance.
	 * Returned panel is displayed by graphical user interface allowing user to confgiure mutation.
	 * This method may return null if no configuration for operator is possible.
	 * 
	 * @return Instance of JPanel with initialized components prepared to configure objects properties.
	 */
	@Override
	public JPanel getConfigPanel() {
		return new HillClimbingConfigPanel(this);
	}

	/**
	 * Returns String object containing description of mutation configuration in readable form as a summary.
	 * 
	 * @return String description of object configuration.
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Mutation class: " + getClass().getSimpleName());
		sb.append("Multiplier: " + multiplier + "\n");
		return sb.toString();
	}
}
